/***************************************************************************
 *   Copyright (C) 2000-2009 by Dead_Body   *
 *   jamesholodnak@gmail.com   *
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 *   This program is distributed in the hope that it will be useful,       *
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
 *   GNU General Public License for more details.                          *
 *                                                                         *
 *   You should have received a copy of the GNU General Public License     *
 *   along with this program; if not, write to the                         *
 *   Free Software Foundation, Inc.,                                       *
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
 ***************************************************************************/

#define JMPBUG
/*#define SKIPBADOPS*/
/*#define MSGFUNC*/
#define log_debug
/************************************************************************************************/
#ifdef _WINDOWS /*prolly msvc (i hope so :P)*/
#pragma warning(disable:4244) /*nasty warning, conversion from bleh to blah, possible loss of data*/
#endif
#include <ctype.h>
#if defined(WIN32)
	#include <windows.h>
	#define SPRINTF wsprintf
#else
	#include <stdio.h>
	#include <string.h>
	#define SPRINTF sprintf
#endif
//#ifdef CPUDEBUG
	#ifdef MSGFUNC
		extern void msg(char *,...);
	#else
		#include <stdio.h>
		#define msg	log_message
	#endif
//#endif
#include "defines.h"
#define __HU6280COMPILED__
#include "hu6280.h"

#define checknz(b)\
	d6502_f &= 0x7D;\
	d6502_f |= nztable[b];
#define push(b)\
	{\
	a = (u32)(d6502_s-- | 0x2100);\
	hu6280_write(a,(u8)(b));\
	}
#define pop(b)\
	{\
	a = (u32)(++d6502_s | 0x2100);\
	(b) = hu6280_read(a);\
	}
#define INLINE						__inline
#define EXTRA_CYCLE				1
#define am_xxx						0
#define op_xxx						0
#define op_xxx_xxx				0
#define NMI_MASK					0xfe
#define IRQ_MASK					0xfd
#define NZ							0x82
#define NZ_MASK					0x7d
#define FLAG_NEGATIVE			0x80
#define FLAG_OVERFLOW			0x40
#define FLAG_XXX					0x20
#define FLAG_BRK					0x10
#define FLAG_DECIMAL				0x08
#define FLAG_IRQDISABLE			0x04
#define FLAG_ZERO					0x02
#define FLAG_CARRY				0x01
#define FLAG_NEGATIVE_MASK		0x7f
#define FLAG_OVERFLOW_MASK		0xbf
#define FLAG_XXX_MASK			0xdf
#define FLAG_BRK_MASK			0xef
#define FLAG_DECIMAL_MASK		0xf7
#define FLAG_IRQDISABLE_MASK	0xfb
#define FLAG_ZERO_MASK			0xfd
#define FLAG_CARRY_MASK			0xfe
#define OFFSET_A					0
#define OFFSET_X					(OFFSET_A + 4)
#define OFFSET_Y					(OFFSET_X + 4)
#define OFFSET_S					(OFFSET_Y + 4)
#define OFFSET_F					(OFFSET_S + 4)
#define OFFSET_PC					(OFFSET_F + 4)
#define OFFSET_TOTALCYCLES		(OFFSET_PC + 4)
#define OFFSET_NEEDIRQ			(OFFSET_TOTALCYCLES + 4)
#define OFFSET_OP6502			(OFFSET_NEEDIRQ + 4)
#define OFFSET_READ6502			(OFFSET_OP6502 + 4)
#define OFFSET_WRITE6502		(OFFSET_READ6502 + 4)
#define OFFSET_OPPAGES			(OFFSET_WRITE6502 + 4)
#define OFFSET_READPAGES		(OFFSET_OPPAGES + (PAGE_NUM * 4))
#define OFFSET_WRITEPAGES		(OFFSET_READPAGES + (PAGE_NUM * 4))
/************************************************************************************************/
typedef void(*voidcall)();
/************************************************************************************************/
static u32 a;
static hu6280_t *data;
static hu6280_t *cpu;
#define d6502_a data->a
#define d6502_x data->x
#define d6502_y data->y
#define d6502_s data->s
#define d6502_f data->f
#define d6502_pc data->pc
#define d6502_eaddr data->eaddr
#define d6502_cycles data->cycles
#define d6502_needirq data->needirq
#ifdef EXECFRAME
#define d6502_framecycles data->framecycles
#define d6502_scanlinecycles data->scanlinecycles
#define d6502_line data->line
static u32 framecycles;
static u32 scanlinecycles;
#endif
static u8 nmicycles;
static u8 irqcycles;
static u32 cyclesexec;
static u16 addr,addrtmp;
static u8 value;
enum addrmodes {er=0,no,ab,ax,ay,ac,im,ix,iy,in,re,zp,zx,zy};
static u8 cycletable[256];
static u8 nztable[256];
static u8 basecycletable[256] =
	{
	7,6,3,4,6,3,5,7,3,2,2,0,7,4,6,6, /* 00-0f */
	2,5,0,4,0,4,6,7,2,4,2,0,0,4,7,6, /* 10-1f */
	6,6,0,4,3,3,5,7,4,2,2,0,4,4,6,6, /* 20-2f */
	2,5,0,0,0,4,6,7,2,4,2,0,0,4,7,6, /* 30-3f */
	6,6,0,4,8,3,5,7,3,2,2,0,3,4,6,6, /* 40-4f */
	2,5,0,5,2,4,6,7,2,4,3,0,0,4,7,6, /* 50-5f */
	6,6,2,5,4,3,5,7,4,2,2,0,5,4,6,6, /* 60-6f */
	2,5,0,17,4,4,6,7,2,4,4,0,7,4,7,6, /* 70-7f */
	3,6,2,5,3,3,3,7,2,2,2,0,4,4,4,6, /* 80-8f */
	2,6,0,8,4,4,4,7,2,5,2,0,5,5,5,6, /* 90-9f */
	2,6,2,5,3,3,3,7,2,2,2,0,4,4,4,6, /* a0-af */
	2,5,7,5,4,4,4,7,2,4,2,0,4,4,4,6, /* b0-bf */
	2,6,2,5,3,3,5,7,2,2,2,0,4,4,6,6, /* c0-cf */
	2,5,0,0,2,4,6,7,2,4,3,0,0,4,7,6, /* d0-df */
	2,6,0,0,3,3,5,7,2,2,2,0,4,4,6,6, /* e0-ef */
	2,5,0,17,0,4,6,7,2,4,3,0,0,4,7,6  /* f0-ff */
	};

/* translate to physical address */
#define TRANSLATE(addr)	\
	((cpu->memorymap[(addr) >> 13] << 13) | ((addr) & 0x1FFF))

/*#define branch(cond)\
{\
value = hu6280_read(d6502_pc);\
d6502_pc++;\
addrtmp = d6502_pc + (s8)value;\
if((addrtmp ^ d6502_pc) & 0xff00)\
	cyclesexec++;\
if(cond)\
	{\
	d6502_pc = addrtmp;\
	cyclesexec++;\
	}\
}*/

#define branch(cond)\
{\
if(cond)\
	{\
	value = hu6280_read(d6502_pc);\
	d6502_pc++;\
	addrtmp = d6502_pc + (s8)value;\
	if((addrtmp ^ d6502_pc) & 0xff00)\
		cyclesexec++;\
	d6502_pc = addrtmp;\
	cyclesexec++;\
	}\
else\
	d6502_pc++;\
}

INLINE u8 hu6280_op(u32 address)
{
	int physaddr = TRANSLATE(address);
	int physbank = physaddr >> 13;

//	log_message("hu6280: op read at $%06X ($%04X)\n",physaddr,address);

	if(cpu->phys_op_page[physbank])
		return(cpu->phys_op_page[physbank][physaddr & 0x1FFF]);
	if(cpu->phys_op_func[physbank])
		return(cpu->phys_op_func[physbank](physaddr));
	log_message("hu6280: unhandled op read at $%06X\n",physaddr);
	return(0);
}

INLINE u8 hu6280_read(u32 address)
{
	int physaddr = TRANSLATE(address);
	int physbank = physaddr >> 13;

//	log_message("hu6280: read at $%06X ($%04X)\n",physaddr,address);

	if(cpu->phys_read_page[physbank])
		return(cpu->phys_read_page[physbank][physaddr & 0x1FFF]);
	if(cpu->phys_read_func[physbank])
		return(cpu->phys_read_func[physbank](physaddr));
	log_message("hu6280: unhandled read at $%06X\n",physaddr);
	return(0);
}

INLINE void hu6280_write(u32 address,u8 data)
{
	int physaddr = TRANSLATE(address);
	int physbank = physaddr >> 13;

//	log_message("hu6280: write at $%06X ($%04X) = $%02X\n",physaddr,address,data);

	if(cpu->phys_write_page[physbank])
		cpu->phys_write_page[physbank][physaddr & 0x1FFF] = data;
	else if(cpu->phys_write_func[physbank])
		cpu->phys_write_func[physbank](physaddr,data);
	else
		log_message("hu6280: unhandled write at $%06X=$%02X\n",physaddr,data);
}

INLINE u16 hu6280_read16(u32 address)
{
	u16 ret = hu6280_read(address+0);
	
	ret |= hu6280_read(address+1) << 8;
//	log_message("hu6280_read16: $%04X = $%04X\n",address,ret);
	return(ret);
}

#include "hu6280_ops.c"

/*
 * hu6280_getvector(int v)
 * 
 * return address of requested vector
 */
u16 hu6280_getvector(u8 v)
{
	switch(v) {
		case CPU_RESET:return(hu6280_read16(0xFFFE));
		case CPU_NMI:return(hu6280_read16(0xFFFC));
		case CPU_TIMER:return(hu6280_read16(0xFFFA));
		case CPU_IRQ1:return(hu6280_read16(0xFFF8));
		case CPU_IRQ2:return(hu6280_read16(0xFFF6));
	}
	return(0);
}

u32 hu6280_getcycles()
{
return(cyclesexec);
}
/************************************************************************************************/
void hu6280_setdata(hu6280_t *newdata)
{
cpu = data = newdata;
}
/************************************************************************************************/
void hu6280_getdata(hu6280_t **newdata)
{
*newdata = data;
}
/************************************************************************************************/
void hu6280_init()
{
	int i;

	for(i=0;i<256;i++) {
		cycletable[i] = basecycletable[i];
		nztable[i] = ((i == 0) ? 2 : (i & 0x80));
	}
	
	//TODO: what are these?
	nmicycles = 7;
	irqcycles = 7;
}

/*
 * hu6280_setirq(int mask)
 * 
 * set needed irq flags to activate irq.
 */
void hu6280_setirq(int mask)
{
	d6502_needirq |= mask;
}

/*
 * hu6280_clearirq(int keepmask)
 * 
 * clear irq flags to deactivate irq.
 */
void hu6280_clearirq(int keepmask)
{
	d6502_needirq &= keepmask;
}

/*
 * hu6280_reset()
 * 
 * reset the cpu.
 */ 
void hu6280_reset()
{
	cpu->memorymap[7] = 0;
	d6502_a = d6502_x = d6502_y = 0;
	d6502_s = 0xFF;
	d6502_f = FLAG_XXX | FLAG_IRQDISABLE;
	d6502_needirq = 0;
	d6502_pc = hu6280_getvector(CPU_RESET);
	d6502_cycles = 0;
	addr = 0;
	log_message("CPU RESET!\n");
}

static u32 burn = 0;
void hu6280_burn(u32 cycles)
{
//	log_message("burning %d cycles\n",cycles);
	burn += cycles;
}

u32 hu6280_step()
{
	u8 op;

	cyclesexec = burn;
	burn = 0;
	if(d6502_needirq) {
		if(d6502_needirq & CPU_NMI) {
			d6502_f &= ~(FLAG_BRK | FLAG_DECIMAL);
			push(d6502_pc >> 8);
			push(d6502_pc);
			push(d6502_f);
			d6502_pc = hu6280_getvector(CPU_NMI);
			cyclesexec += nmicycles;
			d6502_needirq &= NMI_MASK;
			return(cyclesexec);
		}
		if(d6502_needirq & CPU_IRQ1) {
			if((d6502_f & FLAG_IRQDISABLE) == 0) {
				d6502_f &= ~(FLAG_BRK | FLAG_DECIMAL);
				push(d6502_pc >> 8);
				push(d6502_pc);
				push(d6502_f);
				d6502_f |= FLAG_IRQDISABLE;
				d6502_pc = hu6280_getvector(CPU_IRQ1);
				cyclesexec += irqcycles;
				d6502_needirq &= IRQ_MASK;
				return(cyclesexec);
			}
			d6502_needirq &= ~CPU_IRQ1;
		}
	}
	op = hu6280_op(d6502_pc++);
	if(fulloptable[op] != 0)
		fulloptable[op]();
	else {
		d6502_pc--; /*return to the bad opcode*/
		msg("hu6280: bad opcode at $%04X ($%02X)\n",d6502_pc,op);
		d6502_cycles += cyclesexec; /*add in cycles taken*/
		return((u32)cyclesexec | BAD_OPCODE); /* bad opcode */
	}
	cyclesexec += cycletable[op];
	return(cyclesexec);
}
